Ana içeriğe geç
  1. 100 Günde SwiftUI Notları/

7.Gün - Swift Fonksiyonlar - 1 : Parametreler ve Return

Fonksiyon Nedir? #

Fonksiyonları kodumuzun geri kalanından ayrı olarak yazar ve gerektiğinde tekrar tekrar çağırabiliriz.

Örneğin şöyle bir kod grubumuzun olduğunu varsayalım;

print("Welcome to my app!")
print("By default This prints out a conversion")
print("chart from centimeters to inches, but you")
print("can also set a custom range if you want.")

Yukarıdaki bu print() ifadelerini projemizin 3 farklı yerinde kullanmak durumundaysak, sürekli olarak kopyala-yapıştır mı yapacağız? Peki ya bir değişiklik yapmak gerekirse ne olacak? Kopyala yapıştır yaptığımız her yere gidip bu düzeltmeleri mi yapmamız gerekecek? İşte bu gibi problemler için Swift’in -diğer dillerde olduğu gibi- Fonksiyonlar çözümü bulunmakta.

Yukarıdaki kodu bir fonksiyona dönüştürdüğümüzde şu şekilde gözükür;

func showWelcome() {
    print("Welcome to my app!")
    print("By default This prints out a conversion")
    print("chart from centimeters to inches, but you")
    print("can also set a custom range if you want.")
}

Yukarıdaki kodu inceleyelim;

  • Bir fonksiyonun başlangıcını işaretlemek için, func anahtar kelimesi kullanılır.
  • Fonksiyona showWelcome adını veriyoruz. Bu istenilen herhangi bir isim olabilir.
  • Fonksiyonların gövdesi, { } parantezleri arasında yer alır.

() parantezleri, fonksiyon oluşturulurken ve çağrılırken (call) kullanılmaktadır.

Oluşturulan fonksiyonun çağrılması şu şekilde yapılmaktadır.

showWelcome()

Peki parantezler gerçekten ne yapar? Parantezler, fonksiyonun çalışmasını özelleştirmek için kullanılırlar.

let number = 139

if number.isMultiple(of: 2) {
    print("Even")
} else {
    print("Odd")
}

Örneğimizde kullanılan .isMultiple(of:2) fonksiyonu parametre olarak, 2 değerini almış, ve number değişkeninin 2’nin katı olup olmadığını kontrol etmektedir. Eğer .isMultiple(of:) herhangi bir parametre almasaydı biraz saçma bir durum ortaya çıkardı.

Fonksiyon Parametreleri #

() parantezleri arasına ekstra kod koyarak yapılandırmaya açık fonksiyonlar oluşturabiliriz. Verilen sayıyı 1’den 12’ye kadar olan sayılar ile çarpan ve sonucu ekrana yazdıran bir fonksiyon oluşturalım. (Örneğimizde, fonksiyon çağrılırken 5’i kullandık siz istediğinizi seçebilirsiniz.)

func printTimesTables(number: Int) {
    for i in 1...12 {
        print("\(i) x \(number) is \(i * number)")
    }
}

printTimesTables(number: 5)

//ÇIKTI:
//----------------------------------------
// 1 x 5 is 5
// 2 x 5 is 10
// 3 x 5 is 15
// 4 x 5 is 20
// 5 x 5 is 25
// 6 x 5 is 30
// 7 x 5 is 35
// 8 x 5 is 40
// 9 x 5 is 45
// 10 x 5 is 50
// 11 x 5 is 55
// 12 x 5 is 60

Fonksiyon tanımlaması yaptığımız yerde, number: Int kısmı fonksiyonumuzun parametresidir. Bu fonksiyonu çağıran kişinin buraya bir tamsayı girmesi gerektiğini bildiriyoruz. Fonksiyonun içinde ise number değişkeni bir tamsayı olarak kullanılmaktadır.

printTimesTables() çağrısı yapılırken, parametre adını da, fonksiyon çağrısı (number: 5) ile beraber yazmamız gerekiyor. Parametre adının yazılması, çok parametreli fonksiyonlarda kolaylıklar sağlamakta.

func printTimesTables(number: Int, end: Int) {
    for i in 1...end {
        print("\(i) x \(number) is \(i * number)")
    }
}

printTimesTables(number: 5, end: 20)

Genel olarak, fonksiyon tanımlanırken belirlenen değişkenlere parametre , fonksiyon çağrısı yapılırken geçilen değişkenelere argüman adı verilmektedir. Aslında bu isimlendirmenin anlam olarak çok bir farkı yok, bazı insaların her ikisi içinde parametre veya argüman dediğini görebilirsiniz.

Not: Fonksiyon içinde oluşturulan her veri, fonksiyon tamamlandıktan sonra yok edilir.

Fonksiyonların Geri Dönüş Değeri (return) #

Fonksiyonları nasıl oluşturabileceğimizi gördük. Fakat genellikle fonksiyonlar geriye veri de gönderebilirler.

Cocoa kütüphanesinde bulunan sqrt() fonksiyonu, bir sayının karekökünü bize verebilir.

let root = sqrt(169)
print(root)

//ÇIKTI:
//----------------------------------------
// 13.0

sqrt() fonksiyonu bir tane parametre kabul eder, ve aldığı bu sayının karekökünü hesaplayarak geri döndürür.

Eğer kendi fonksiyonumuzda bir değer döndürmek istiyorsak, yapmamız gerekenler şunlardır;

  1. Fonksiyonumuzun başlangıç süslü parantezinden önce bir ok yapın ve hangi veri türünü döndüreceğini yazın.
  2. Veriyi döndürmek için return anahtar kelimesini kullanın.
func rollDice() -> Int {
    return Int.random(in: 1...6)
}

let result = rollDice()
print(result)

Yukarıdaki örnekte, rollDice() fonksiyonun, Int türünde bir değer geri döndüreceğini bildirdik, bu sebeple return ile mutlaka Int türünde bir geri dönüş sağlamalıyız.

Şöyle bir örnek yapalım;

Fonksiyonumuz iki adet String’i parametre olarak alsın, iki adet String’in harflerine baksın ve aynı harflerden oluşup oluşmadığını kontrol etsin, eğer aynı harflerden oluşuyorsa true değerini geri döndürsün. Mesela “abc” ve “cab” Stringlerinin sonucu true olmalıdır.

func areLettersIdentical(string1: String, string2: String) -> Bool {
    let first = string1.sorted()
    let second = string2.sorted()
    return first == second
}

Yukarıdaki kodu biraz daha sadeleştirebiliriz. Yukarıdaki first ve second değişkenlerini kullanmasak da olur.

func areLettersIdentical(string1: String, string2: String) -> Bool {
    return string1.sorted() == string2.sorted()
}

Yukarıdaki kodu daha da kısaltabiliriz;

func areLettersIdentical(string1: String, string2: String) -> Bool {
    string1.sorted() == string2.sorted()
}

Tek satırdan oluşan fonksiyonlarda return ’ün kullanılmasına gerek yoktur. Bu, yalnızca fonksiyon tek bir kod satırı içerdiğinde ve bu kod satırı gerçekten döndürmeyi belirttiğiniz veriyi döndürdüğünde çalışır. Yani tek satırlık expression kullanıldığında return ’e gerek yoktur.

func greet(name: String) -> String {
    if name == "Taylor Swift" {
        return "Oh wow!"
    } else {
        return "Hello, \(name)"
    }
}

Yukarıdaki koddan return ’ü kaldırmak istediğimizde, bunu yapamayız çünkü return bir statement ifadeden geliyor. Fakat kodumuzu ternary operatör kullanarak refactor edersek return ’ü kaldırabiliriz.

func greet(name: String) -> String {
    name == "Taylor Swift" ? "Oh wow!" : "Hello, \(name)"
}

İPUCU : return anahtar kelimesini, fonksiyonumuz bir değer döndürmerse de kullanabiliriz. Bu durumda fonksiyonu çıkış yapmaya zorlamış oluruz.

Expression ve Statements Arasındaki Fark #

Kodumuz, true, false, “Hello” veya 19 gibi tek bir değere indirgenebiliyorsa buna expression diyoruz. Expression ‘lar bir değişkene atanabilir veya print() kullanılarak yazdırılabilen şeylerdir.

let isAdmin = true 
let isOwner = false
let isEditingEnabled = false

//expression
isOwner == true && isEditingEnabled || isAdmin == true 
//tamamı true değerini verir.

Diğer yandan, değişken oluşturmak, döngü başlatmak veya bir koşulu kontrol etmek gibi eylemlere ise statement denilmektedir.

//statement
let name = "Otis"

Fonksiyondan Çoklu Değer Döndürme #

Fonksiyonlardan çoklu değer geri döndürmek için tuple kullanılır. Array, Dictionary ve Set gibi, Tuple ‘lar da tek bir değişkene birden çok veri koymamızı sağlar. Ancak Tuple ‘lar diğerlerinden farklı olarak, sabit bir boyuta ve çoklu veri türüne sahip olabilirler.

Elbette çoklu geri dönüş için, Array veya Dictionary de kullanabiliriz. Fakat bu çirkin bir görüntüye ve kafa karışıklığına sebep olabilir. Çünkü, Array’de istediğimiz verinin index ‘ini bilmemiz gerekir, Dictionary’de ise hem key ‘i bilmek hem de default değer sağlamak zorunda kalırız. Tuple’lar ise fonksiyonlarda çoklu değer dönüşü için oldukça iyi bir şekilde kullanılabilmektedir.

func getUser() -> (firstName: String, lastName: String) {
    (firstName: "Taylor", lastName: "Swift")
}

let user = getUser()
print("Name: \(user.firstName) \(user.lastName)")

Şimdi yukarıdaki kodu inceleyelim;

  1. Dönüş tipimiz: (firstName: String, lastName: String) yani iki String içeren bir Tuple
  2. Tuple ‘da kullanılan her bir String’in, kendine has bir adı vardır. Bu ad, Dictionary’deki key ‘in aksine “” tırnak içinde değildir.
  3. Fonksiyonumuzun içinde, geri dönüş için söz verdiğimiz tüm öğelerin değelerini belirleyip gönderiyoruz.
  4. getUser() fonksiyonunu çağırdığımızda tuple’ın değerlerini key vererek okuyabiliriz.

Evet bu noktada tuple ‘ların, Dictionary ‘lere benzediğini düşünebiliriz, fakat bazı farklar mevcut;

  1. Bir Dictionary’de değerlere eriştiğimizde, Swift bunların mevcut olup olmadığını önceden bilemez. Dictionary key ‘ini verdiğimizde, bir şeyler geri dönebileceğini o an biliyor olabiliriz, fakat Swift emin olamaz bu sebeple default bir değer sağlamamız gerekir.
  2. Bir Tuple ‘daki değere eriştiğimizde, Swift tuple değerinin kullanılabilir olduğunu önceden bilir, bu yüzden default değer vermemize gerek olmaz.
  3. Değerlere user.firstName şeklinde erişebiliriz. Bu bir String değildir bu nedenle yazım hatası yapma şansıda yoktur. (Xcode’un user. yazdıktan sonra kodu tamamlamak için öneride bulunduğunu görebilirsiniz.)
  4. Dictionary bir sürü farklı değer içerebilir, ancak Tuple bunu yapamaz. Tuple ‘ın içereceği tüm verinin listelenmesi, hepsini içermesi ve başka hiçbir şey içermemesi gerekir. Yani Tuple ilk olarak oluşturulurken değerleri atanır ve bir daha değitirilemez.

İşte bu noktada Tuple ‘ın, Dictionary ‘ye göre önemli bir avantajı vardır: hangi değerlerin var olacağını ve hangi türlere sahip olduklarını tam olarak belirtebiliriz, oysa Dictionary istediğimiz değerleri içerebilir veya içermeyebilir.

Tuple Kullanılırken Dikkat Edilecekler #

  • Bir fonksiyondan Tuple döndürürken, Swift zaten Tuple element isimlerini biliyor. Bu sebeple tekrar yazmamıza gerek yoktur. Yukarıdaki örneği aynen şu şekilde de yazabiliriz.
func getUser() -> (firstName: String, lastName: String) {
    ("Taylor", "Swift")
}
  • Bazen, element isimlerinin olmadığı Tuple kullanımları görebiliriz. Bu durumda Tuple elemanlarına 0’dan başlayan sayısal indisler kullanarak erişebiliriz. Bu sayısal indisler, adlandırılmış Tuple ‘larda da kullanılabilir.
func getUser() -> (String, String) {
    ("Taylor", "Swift")
}

let user = getUser()
print("Name: \(user.0) \(user.1)")
  • Fonksiyon, bir Tuple döndürüyorsa, Tuple ‘ı ayrı ayrı değerlere ayırabiliriz. Aslında burada yeni bir şey yok sadece veriyi biraz gezdirmiş olduk.
func getUser() -> (firstName: String, lastName: String) {
    (firstName: "Taylor", lastName: "Swift")
}

let user = getUser()
let firstName = user.firstName
let lastName = user.lastName

print("Name: \(firstName) \(lastName)")

Yine de Tuple ‘ı user ’a atamak ve değerleri tek tek kopyalamak yerine, getUser() fonksiyonundan geri dönen Tuple ‘ı iki ayrı sabite ayırabiliriz.

let (firstName, lastName) = getUser()
print("Name: \(firstName) \(lastName)")

Son olarak, geri dönen Tuple ‘daki tüm değerlere ihtiyacımız yoksa, Swift’e Tuple’daki bir kısmı yok saymasını söylemek için _ kullanırız.

let (firstName, _) = getUser()
print("Name: \(firstName)")

Fonksiyon Parametre İsimlendirmesinin Özelleştirilmesi #

Swift Fonksiyon Parametre İsimleri

Swift’te oluşturulan fonksiyonların Harici ve Dahili parametre isimleri ayrı ayrı belirlenebilir. Harici isim,fonksiyon çağrısı yapılırken, dahili isim ise fonksiyonun gövdesi içinde işlem yapılırken kullanılır.

Swift’te fonksiyonların harici parametre isimlendirmesi o kadar yaygındır ki, hangi metodun çağrılacağına karar verilirken bu isimler kullanılır.

func hireEmployee(name: String) { }
func hireEmployee(title: String) { }
func hireEmployee(location: String) { }

Evet, yukarıdakilerin hepsi hireEmployee() fonksiyonudur. Fakat Swift, verdiğimiz parametre ismine göre hangisini kastettiğimizi bilir.

Bazen de fonksiyon çağrısı yaparken, parametre ismi vermek istemeyiz. hasPrefix() fonksiyonunu düşünelim

let lyric = "I see a red door and I want it painted black"
print(lyric.hasPrefix("I see"))

İşte hasPrefix() fonksiyonunda, parametre ismi kullanmamızı _ sağlamıştır. Kendi oluşturduğumuz fonksiyonlarda da bunu kullanabiliriz. Harici parametre ismi yerine _ yazıldığına dikkat edin.

func isUppercase(_ string: String) -> Bool {
    string == string.uppercased()
}

let string = "HELLO, WORLD"
let result = isUppercase(string)

Bazen de, kodumuzun okunabilirliğini daha da arttırmak için harici parametre ismi kullanmak isteyebiliriz

func printTimesTables(number: Int) {
    for i in 1...12 {
        print("\(i) x \(number) is \(i * number)")
    }
}

printTimesTables(number: 5)

Yukarıdaki kod Swift için geçerli bir koddur. Fakat okunabilirlik olarak biraz zayıf kalıyor. Yukarıdaki kodu, okunabilirliği arttıracak şekilde, harici parametre isimlendirmesi kullanarak yeniden yazabiliriz.

func printTimesTables(for number: Int) {
    for i in 1...12 {
        print("\(i) x \(number) is \(i * number)")
    }
}

printTimesTables(for: 5)

Burada dikkat etmemiz gereken şeylere yakından bakalım;

  1. for number: Int kısmında harici isim for , dahili isim ise number ’dır.
  2. Fonksiyonu çağırdığımızda, parametre için harici ismi kullanırız: printTimesTables(for: 5)
  3. Fonksiyonun içinde, dahili ismi kullanırız: print("\(i) x \(number) is \(i * number)")

Bu yazıyı İngilizce olarak da okuyabilirsiniz.
You can also read this article in English.

Bu yazı, SwiftUI Day 7 adresinde bulunan yazılardan kendim için aldığım notları içermektedir. Orjinal dersi takip etmek için lütfen bağlantıya tıklayın.